package forum.dao.file;

import forum.dao.GenericDAO;
import forum.entities.Entity;
import forum.exceptions.CreateExistingEntityException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;

public abstract class FileGenericDAO<T extends Entity> implements GenericDAO<T> {

    private String entityTagName;
    private File dataFile;
    private DocumentBuilder builder;
    private Document document;
    private Transformer transformer;
    private static BigInteger lastInsertedKey;

    protected FileGenericDAO(String fileName,String rootTagName,String entityTagName) {
        this.entityTagName = entityTagName;
        dataFile = new File(fileName);
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setIgnoringElementContentWhitespace(true);
            builder = dbf.newDocumentBuilder();
            transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "no");
        //    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            if (!dataFile.exists()) {
                lastInsertedKey = BigInteger.ZERO;
                document = builder.newDocument();
                Element rootElement = document.createElement(rootTagName);
                rootElement.setAttribute("lastId",lastInsertedKey.toString());
                document.appendChild(document.createElement(rootTagName));
                DOMSource xmlSource = new DOMSource(document);
                StreamResult result = new StreamResult(new File(fileName));
                transformer.transform(xmlSource,result);
            } else {
                document = builder.parse(dataFile);
                Element root = document.getDocumentElement();
                lastInsertedKey = new BigInteger(root.getAttribute("lastId"));
            }
        } catch (ParserConfigurationException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (TransformerConfigurationException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (TransformerException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
    }

    @Override
    public T create() {
        T entity = newEntity();
        try {
            document = builder.parse(dataFile);
            Element root = document.getDocumentElement();
            root.normalize();
            root.setAttribute("lastId",lastInsertedKey.toString());
            root.appendChild(mapEntityToElement(document, root, entity));
            DOMSource xmlSource = new DOMSource(document);
            StreamResult result = new StreamResult(dataFile);
            transformer.transform(xmlSource, result);
        } catch (SAXException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }  catch (TransformerException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        return entity;
    }

    @Override
    public T read(BigInteger id) {
        T foundEntity = null;
        try {
            document = builder.parse(dataFile);
            Element root = document.getDocumentElement();
            root.normalize();
            NodeList entities = root.getElementsByTagName(entityTagName);
            for (int i = 0; i < entities.getLength(); i++) {
                Element tempElement = (Element) entities.item(i);
                if (tempElement.getElementsByTagName("id").item(0).getTextContent().equals(id.toString())) {
                    foundEntity = mapElementToEntity(document,tempElement);
                }
            }
        } catch (SAXException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        return foundEntity;
    }

    @Override
    public void update(T entity) throws CreateExistingEntityException {
        if (entity.getId() == null) {
            throw new IllegalArgumentException("Can't update not existing entity");
        }
        try {
            document = builder.parse(dataFile);
            Element root = document.getDocumentElement();
            root.normalize();
            NodeList entities = root.getElementsByTagName(entityTagName);
            for (int i = 0; i < entities.getLength(); i++) {

                Element tempNode = (Element)entities.item(i);

                if (tempNode.getElementsByTagName("id").item(0).getTextContent().equals(entity.getId().toString())) {
                    root.replaceChild(mapEntityToElement(document, root, entity), entities.item(i));
                }
            }
            DOMSource xmlSource = new DOMSource(document);
            StreamResult result = new StreamResult(dataFile);
            transformer.transform(xmlSource, result);
        } catch (SAXException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (TransformerException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    @Override
    public void delete(BigInteger id) {
        if (id == null) {
            return;
        }
        try {
            document = builder.parse(dataFile);
            Element root = document.getDocumentElement();
            root.normalize();
            NodeList entities = root.getElementsByTagName(entityTagName);
            for (int i = 0; i < entities.getLength(); i++) {
                Element tempNode = (Element)entities.item(i);
                if (tempNode.getElementsByTagName("id").item(0).getTextContent().equals(id.toString())) {
                    root.removeChild(tempNode);
                }
            }
            DOMSource xmlSource = new DOMSource(document);
            StreamResult result = new StreamResult(dataFile);
            transformer.transform(xmlSource, result);
        } catch (SAXException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (IOException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } catch (TransformerException e) {
            // TODO auto-generated try-catch block
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    protected String getEntityTagName() {
        return entityTagName;
    }

    protected DocumentBuilder getBuilder() {
        return builder;
    }

    protected File getDataFile() {
        return dataFile;
    }

    protected static BigInteger getNewKey() {
        lastInsertedKey = lastInsertedKey.add(BigInteger.ONE);
        return lastInsertedKey;
    }

    protected abstract Element mapEntityToElement(Document document, Element parentElement, T entity);

    protected abstract T mapElementToEntity(Document document, Element entityElement);

    protected abstract T newEntity();


}
